/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "XMPlayer.h"
#include "OrderedReader.h"
#include "XMChannel.h"

#ifdef SAMPLE_WRITER
#include <filesystem/File.h>
void * xm_data_for_sample_writer = 0;
#endif

XMPlayer::XMPlayer(resource::Resource *resource, const Format & format, bool interpolate )
:	format( format ),
	interpolate( interpolate ),
	valid( false )
{
	//resource = ResourceManager::create_resource( file_name + ".xm" );
	if( !resource )
		return;

	#ifdef SAMPLE_WRITER
	xm_data_for_sample_writer = new char[resource->get_size()];
	resource->read( xm_data_for_sample_writer, resource->get_size() );
	resource->reset();
	#endif

	//test file signature
	char signature_buf[17];
	resource->read( signature_buf, 17 );
	std::string signature(signature_buf);
	if( signature != "Extended Module: " && signature != "Advanced Module: ")
		return;
	
	//get name of module
	char module_name_buf[20];
	resource->read( module_name_buf, 20 );
	module_name = module_name_buf;

	//skipping $1a (this is symbol in file)
	resource->skip( 1 );

	//get tracker name
	char tracker_name_buf[20];
	resource->read( tracker_name_buf, 20 );
	tracker_name = tracker_name_buf;

	//get tracker version
	tracker_version = read_short_little_endian( resource );

	unsigned header_size = read_long_little_endian( resource );
	//get header size

	//get song length
	song_length = read_short_little_endian( resource );

	//get restart position
	restart_position = read_short_little_endian( resource );

	//get number of channels
	unsigned short number_of_channels = read_short_little_endian( resource );

	int i;
	for(i = 0; i < number_of_channels; ++i )
	{
		channels.push_back( new XMChannel( this, i ) );//, MixerPtr().operator->(), Format( 44100, 1, 8 ) ) );
	}

	//get number of patterns
	unsigned short number_of_patterns = read_short_little_endian( resource );

	//get number of instruments
	unsigned short number_of_instruments = read_short_little_endian( resource );

	//flags (0 - amiga freq, 1 - linear freq)
	unsigned short flag = read_short_little_endian( resource );
	use_linear_table = !!flag;

	//get default tempo
	original_speed = read_short_little_endian( resource );

	//get default bmp
	original_bpm = read_short_little_endian( resource );

	//get pattern order
	pattern_order.resize( 256 );
	for( i = 0; i < 256; ++i )
	{
		unsigned char byte; 
		resource->read( &byte, 1 );
		pattern_order[i] = byte;
	}

	//reading patterns
	patterns.reserve( number_of_patterns );
	for( i = 0; i < number_of_patterns; ++i )
		patterns.push_back( XMPattern( number_of_channels, resource ) );
	//patterndata readed

	//reading instruments
	instruments.reserve( number_of_instruments );
	for( i = 0; i < number_of_instruments; ++i )
		instruments.push_back( new XMInstrument( resource ) );

	global_volume = 128;
	current_speed = original_speed;
	current_bpm = original_bpm;

	current_pattern_index = 0;
	next_pattern_index = 0;
	real_pattern = pattern_order[ current_pattern_index ];

	pattern_delay = 0;
	next_read_note_tick = 0;
	buffer_size_before_next_update = 0;
	current_row = -1;

	valid = true;

	#ifdef SAMPLE_WRITER
	filesystem::File fw( "stripped_module.xm", filesystem::File::WRITE );
	fw.write( xm_data_for_sample_writer, resource->get_size() );
	delete [] xm_data_for_sample_writer; xm_data_for_sample_writer = 0;
	#endif
}

XMPlayer::~XMPlayer()
{
	unsigned int i;
	for( i = 0; i < channels.size(); ++i )
	{
		delete channels[i]; channels[i] = 0;
	}

	for( i = 0; i < instruments.size(); ++i )
	{
		delete instruments[i]; instruments[i] = 0;
	}
}

bool XMPlayer::advance_row()
{
	if( real_pattern >= (int)patterns.size() || current_pattern_index >= song_length )
		return false;

	++current_row;
	if( current_row >= patterns[real_pattern].get_rows_count() )
		current_row = 0;
	if( !current_row )
	{
		current_pattern_index = next_pattern_index;
		++next_pattern_index;
		real_pattern = pattern_order[ current_pattern_index ];
		while( real_pattern == 254 )
		{
			current_pattern_index = next_pattern_index;
			++next_pattern_index;
			real_pattern = pattern_order[ current_pattern_index ];
		}
		if( real_pattern >= (int)patterns.size() || current_pattern_index >= song_length )
			return false;
	}

	return true;
}

void XMPlayer::reset()
{
	current_speed = original_speed;
	current_bpm = original_bpm;
	current_row = -1;

	current_pattern_index = restart_position;
	next_pattern_index = restart_position + 1;
	real_pattern = pattern_order[ current_pattern_index ];
}

int XMPlayer::read( void * where, int samples_count )
{
	if( !where || !samples_count )
		return 0;
	int samples_count_orignal = samples_count;
	char * dst = (char *)where;
	while( samples_count )
	{
		//test end of patterns
		if( !buffer_size_before_next_update )
		{
			if( !next_read_note_tick )
			{
				if( !advance_row() )
					return samples_count_orignal - samples_count;

				int pattern = real_pattern;
				int row = current_row;
				for( unsigned int i = 0; i < channels.size(); ++i )
					channels[i]->update_note( patterns[ pattern ].get_note( row, i ) );

				next_read_note_tick = current_speed + pattern_delay * current_speed;
				pattern_delay = 0;
			}

			for( unsigned int i = 0; i < channels.size(); ++i )
				channels[i]->update_tick();

			for( unsigned int i = 0; i < channels.size(); ++i )
				channels[i]->update_volume();

			buffer_size_before_next_update = (format.get_frequency() * 5) / (current_bpm * 2);
			--next_read_note_tick;
		}

		int samples_needed = buffer_size_before_next_update > samples_count ? samples_count : buffer_size_before_next_update;
		for( unsigned int i = 0; i < channels.size(); ++i )
			channels[i]->decode( dst, samples_needed, format, interpolate );
		dst += samples_needed * format.get_sample_size();
		buffer_size_before_next_update -= samples_needed;
		samples_count -= samples_needed;
	}

	return samples_count_orignal;
}